﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MemberTypesExtensions.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Security;

    using UCodeFirst.Converters;
    using UCodeFirst.MemberTypes;

    using umbraco.cms.businesslogic.member;

    using Umbraco.Core;

    /// <summary>
    /// Holds extension methods for <see cref="MemberTypeBase"/>.
    /// </summary>
    public static class MemberTypesExtensions
    {
        /// <summary>
        /// List of loaded member types types by alias.
        /// Bug: Forces the search based on string instead of the type itself.
        /// </summary>
        private static readonly Lazy<IDictionary<string, Type>> MemberTypeTypesByAlias = new Lazy<IDictionary<string, Type>>(
            () => TypesScanner.TypesInBin.Value.Where(x => x.GetAllBaseTypes().Select(y => y.FullName).InvariantContains("UCodeFirst.MemberTypes.MemberTypeBase")).ToDictionary(x => x.Name), true);

        /// <summary>
        /// Returns a strongly-typed <typeparamref name="T"/> member.
        /// </summary>
        /// <param name="membershipUser">
        /// The membership user.
        /// </param>
        /// <typeparam name="T">
        /// The type of member type.
        /// </typeparam>
        /// <returns>
        /// The instantiated member.
        /// </returns>
        public static T ToStrongMember<T>(this MembershipUser membershipUser)
            where T : MemberTypeBase
        {
            var member = ToStrongMember(membershipUser);

            if (member == null)
            {
                return null;
            }

            if (member is T)
            {
                return (T)member;
            }

            throw new UCodeFirstException(Resources.UnableToCastObject.InvariantFormat(member.GetType(), typeof(T)));
        }

        /// <summary>
        /// Returns a strongly-typed member.
        /// </summary>
        /// <param name="membershipUser">
        /// The membership user.
        /// </param>
        /// <returns>
        /// The instantiated member.
        /// </returns>
        public static MemberTypeBase ToStrongMember(this MembershipUser membershipUser)
        {
            if (membershipUser == null)
            {
                return null;
            }

            return ToStrongMember(umbraco.providers.members.Helper.GetMemberByUsernameOrGuid(membershipUser.UserName));
        }

        /// <summary>
        /// Returns a strongly-typed <typeparamref name="T"/> member.
        /// </summary>
        /// <param name="umbracoMember">
        /// The umbraco member.
        /// </param>
        /// <typeparam name="T">
        /// The type of member type.
        /// </typeparam>
        /// <returns>
        /// The instantiated member.
        /// </returns>
        public static T ToStrongMember<T>(this Member umbracoMember)
            where T : MemberTypeBase
        {
            var member = ToStrongMember(umbracoMember);

            if (member == null)
            {
                return null;
            }

            if (member is T)
            {
                return (T)member;
            }

            throw new UCodeFirstException(Resources.UnableToCastObject.InvariantFormat(member.GetType(), typeof(T)));
        }

        /// <summary>
        /// Returns a strongly-typed member.
        /// </summary>
        /// <param name="umbracoMember">
        /// The umbraco member.
        /// </param>
        /// <returns>
        /// The instantiated member.
        /// </returns>
        public static MemberTypeBase ToStrongMember(this Member umbracoMember)
        {
            if (umbracoMember == null || umbracoMember.ContentType == null || umbracoMember.ContentType.Alias == null || umbracoMember.Id == 0)
            {
                return null;
            }

            if (!MemberTypeTypesByAlias.Value.ContainsKey(umbracoMember.ContentType.Alias))
            {
                throw new UCodeFirstException(Resources.UnableToFindMemberType.InvariantFormat(umbracoMember.ContentType.Alias));
            }

            var memberTypeType = MemberTypeTypesByAlias.Value[umbracoMember.ContentType.Alias];
            var member = memberTypeType.CreateInstance<MemberTypeBase>();

            member.Member = umbracoMember;

            MapBaseProperties(umbracoMember, member);

            foreach (var umbracoMemberProperty in umbracoMember.GenericProperties)
            {
                var targetPropertyInfo = memberTypeType.GetProperty(umbracoMemberProperty.PropertyType.Alias);
                if (targetPropertyInfo == null)
                {
                    // property alias might have been converted to camel case by Umbraco.
                    targetPropertyInfo = memberTypeType.GetProperty(umbracoMemberProperty.PropertyType.Alias.FirstCharacterToUpper());
                    if (targetPropertyInfo == null)
                    {
                        continue;
                    }
                }

                var targetPropertyAttr = targetPropertyInfo.GetAttr<PropertyAttribute>();
                if (targetPropertyAttr == null)
                {
                    continue;
                }

                var converter = targetPropertyAttr.Converter == null
                                    ? StandardConverters.Converters.ContainsKey(targetPropertyInfo.PropertyType) ? StandardConverters.Converters[targetPropertyInfo.PropertyType] : null
                                    : targetPropertyAttr.Converter.CreateInstance<IConverter>();

                if (converter == null)
                {
                    throw new UCodeFirstException(Resources.UnableToFindConverter.InvariantFormat(umbracoMemberProperty.PropertyType.Alias));
                }

                var setMethod = targetPropertyInfo.GetSetMethod(true);
                if (setMethod == null)
                {
                    throw new UCodeFirstException(Resources.UnableToFindSetMethod.InvariantFormat(umbracoMemberProperty.PropertyType.Alias));
                }
                
                setMethod.Invoke(member, new[] { converter.Read(targetPropertyInfo, umbracoMemberProperty.Value.ToString()) });
            }

            return member;
        }

        /// <summary>
        /// Map common properties between <see cref="Member"/> and <see cref="MemberTypeBase"/>.
        /// </summary>
        /// <param name="umbracoMember">
        /// The umbraco member.
        /// </param>
        /// <param name="member">
        /// The member.
        /// </param>
        private static void MapBaseProperties(Member umbracoMember, MemberTypeBase member)
        {
            member.Id = umbracoMember.Id;
            member.LoginName = umbracoMember.LoginName;
            member.Name = umbracoMember.Text;
            member.Email = umbracoMember.Email;
            member.Password = umbracoMember.Password;
        }
    }
}
